﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.QCode.QEncoder;

namespace Noris.Tools.QCode.QrEncoder
{
    #region class QrEncoder
    /// <summary>
    /// QR code encoder.
    /// More on: http://en.wikipedia.org/wiki/QR_code
    /// </summary>
    public partial class QrEncoder
    {
        #region Encode to QrCode
        /// <summary>
        /// Encode given content with default (High) ErrorCorrectionLevel
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public static QrCode Encode(string content)
        { return _Encode(content, ErrorCorrectionLevel.Medium, null); }
        /// <summary>
        /// Encode given content with given ErrorCorrectionLevel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="errorCorrectionLevel"></param>
        /// <returns></returns>
        public static QrCode Encode(string content, ErrorCorrectionLevel errorCorrectionLevel)
        { return _Encode(content, errorCorrectionLevel, null); }
        /// <summary>
        /// Encode given content with default (High) ErrorCorrectionLevel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        public static QrCode Encode(string content, Int32? useMatrixPattern)
        { return _Encode(content, ErrorCorrectionLevel.Medium, useMatrixPattern); }
        /// <summary>
        /// Encode given content with given ErrorCorrectionLevel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="errorCorrectionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        public static QrCode Encode(string content, ErrorCorrectionLevel errorCorrectionLevel, Int32? useMatrixPattern)
        { return _Encode(content, errorCorrectionLevel, useMatrixPattern); }
        /// <summary>
        /// Encode given content with default (High) ErrorCorrectionLevel
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static QrCode Encode(IQrMessageFormats message)
        { return _Encode(message.QrMessage, ErrorCorrectionLevel.Medium, null); }
        /// <summary>
        /// Encode given content with given ErrorCorrectionLevel
        /// </summary>
        /// <param name="message"></param>
        /// <param name="errorCorrectionLevel"></param>
        /// <returns></returns>
        public static QrCode Encode(IQrMessageFormats message, ErrorCorrectionLevel errorCorrectionLevel)
        { return _Encode(message.QrMessage, errorCorrectionLevel, null); }
        /// <summary>
        /// Encode given content with default (High) ErrorCorrectionLevel
        /// </summary>
        /// <param name="message"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        public static QrCode Encode(IQrMessageFormats message, Int32? useMatrixPattern)
        { return _Encode(message.QrMessage, ErrorCorrectionLevel.Medium, useMatrixPattern); }
        /// <summary>
        /// Encode given content with given ErrorCorrectionLevel
        /// </summary>
        /// <param name="message"></param>
        /// <param name="errorCorrectionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        public static QrCode Encode(IQrMessageFormats message, ErrorCorrectionLevel errorCorrectionLevel, Int32? useMatrixPattern)
        { return _Encode(message.QrMessage, errorCorrectionLevel, useMatrixPattern); }
        /// <summary>
        /// Encode context with errorLevel and matrix pattern.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="errorCorrectionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        private static QrCode _Encode(string content, ErrorCorrectionLevel errorCorrectionLevel, Int32? useMatrixPattern)
        {
            ErrorCorrectionLevelInternal level = ErrorCorrectionLevelConverter.ToInternal(errorCorrectionLevel);
            QRCodeInternal qrCodeInternal = new QRCodeInternal();
            EncoderInternal.Encode(content, level, useMatrixPattern, qrCodeInternal);
            return new QrCode(qrCodeInternal);
        }
        #endregion
    }    
    #endregion
    #region class QrCode
    /// <summary>
    /// Data of QR code
    /// </summary>
    public class QrCode
    {
        /// <summary>
        /// Konstruktor je internal, na základě interní třídy obsahující již hotový kód
        /// </summary>
        /// <param name="qrCodeInternal"></param>
        internal QrCode(QRCodeInternal qrCodeInternal)
        {
            BitMatrix matrix = new BitMatrix(qrCodeInternal.MatrixWidth);
            for (int i = 0; i < qrCodeInternal.MatrixWidth; i++)
                for (int j = 0; j < qrCodeInternal.MatrixWidth; j++)
                    matrix[i, j] = (qrCodeInternal.Matrix.get_Renamed(i, j) != 0);
            this.Matrix = matrix;
        }
        /// <summary>
        /// Fyzická matrice QR kódu - bez rámečku
        /// </summary>
        public BitMatrix Matrix { get; private set; }
        /// <summary>
        /// Vrátí matrici QR kódu s rámečkem dané šířky.
        /// Vstupní hodnotu zarovná do mezí 0 ÷ 32.
        /// </summary>
        /// <param name="frameWidth"></param>
        /// <returns></returns>
        public BitMatrix GetFramedMatrix(int frameWidth)
        {
            int fw = GetAlignedFrameWidth(frameWidth);
            BitMatrix source = this.Matrix;
            int width = source.Width;
            int size = width + 2 * fw;
            BitMatrix matrix = new BitMatrix(size);
            for (int i = 0; i < width; i++)
                for (int j = 0; j < width; j++)
                    matrix[i + fw, j + fw] = source[i, j];
            return matrix;
        }
        /// <summary>
        /// Vrátí šířku rámečku zarovnanou do platných mezí 0 ÷ 32
        /// </summary>
        /// <param name="frameWidth"></param>
        /// <returns></returns>
        public static int GetAlignedFrameWidth(int frameWidth)
        {
            return (frameWidth < 0 ? 0 : (frameWidth > 32 ? 32 : frameWidth));
        }
    }    
    #endregion
    #region class BitMatrix
    /// <summary>
    /// Class containing bit matrix of QR code
    /// </summary>
    public partial class BitMatrix
    {
        #region Základní data
        /// <summary>
        /// Create a new matrix
        /// </summary>
        /// <param name="width"></param>
        public BitMatrix(int width)
        {
            this._Matrix = new bool[width, width];
        }
        private readonly bool[,] _Matrix;
        /// <summary>
        /// get a value from given cell of this matrix
        /// </summary>
        /// <param name="i"></param>
        /// <param name="j"></param>
        /// <returns></returns>
        public bool this[int i, int j]
        {
            get { return this._Matrix[i, j]; }
            internal set { this._Matrix[i, j] = value; }
        }
        /// <summary>
        /// Width (eq to Height) of this matrix
        /// </summary>
        public int Width
        {
            get { return _Matrix.GetLength(0); }
        }
        #endregion
    }    
    #endregion
    #region enum ErrorCorrectionLevel and class ErrorCorrectionLevelConverter
    /// <summary>
    /// Error Correction Level of QR code.
    /// More on: http://en.wikipedia.org/wiki/QR_code
    /// </summary>
    public enum ErrorCorrectionLevel
    {
        /// <summary>
        /// (0) Level L (Low);
        /// 7% of codewords can be restored.
        /// </summary>
        Low = 0,
        /// <summary>
        /// (1) Level M (Medium);
        /// 15% of codewords can be restored.
        /// </summary>
        Medium = 1,
        /// <summary>
        /// (2) Level Q (Quartile);
        /// 25% of codewords can be restored.
        /// 
        /// </summary>
        Quartile = 2,
        /// <summary>
        /// (3) Level H (High);
        /// 30% of codewords can be restored.
        /// </summary>
        High = 3
    }
    internal static class ErrorCorrectionLevelConverter
    {
        internal static ErrorCorrectionLevel FromInternal(ErrorCorrectionLevelInternal level)
        {
            return (ErrorCorrectionLevel)level.ordinal();
        }
        internal static ErrorCorrectionLevelInternal ToInternal(ErrorCorrectionLevel level)
        {
            switch (level)
            {
                case ErrorCorrectionLevel.Low:
                    return ErrorCorrectionLevelInternal.L;
                case ErrorCorrectionLevel.Medium:
                    return ErrorCorrectionLevelInternal.M;
                case ErrorCorrectionLevel.Quartile:
                    return ErrorCorrectionLevelInternal.Q;
                case ErrorCorrectionLevel.High:
                    return ErrorCorrectionLevelInternal.H;
            }
            throw new NotSupportedException(string.Format("Error correction level {0} is not supported.", level));
        }

    }
    #endregion
}
